iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Modern Web

Three.js × WebGL 實戰:打造 HD-2D 像素地下城與即時光影系統系列 第 14 篇

Day 14:靈魂點睛之筆!物理燭火搖曳效果與動態光源效能優化

  • 分享至 

  • xImage
  •  

在 Day 13 中,我們成功鋪設了深藍紫色的冷調環境底光,並透過高解析度的 PCF 軟陰影映射,讓斜射的月光在廢墟中拉出修長的陰影,奠定了地窖深邃神祕的氛圍。然而,如果整個場景只有冰冷的月光,這座古堡看起來就會像是一座毫無生機的死城,缺乏吸引玩家探索的「冒險感」。

在經典的 HD-2D 美術設計中,最迷人的畫面往往建立在強烈的「冷暖對比(Chiaroscuro)」之上——在幽暗寒冷的迷霧中,點綴幾盞散發著橘黃色暖光的火把或壁燈。今天,我們將在 Day 12 預先記錄的牆壁錨點上,安裝實體的鍛鐵壁燈,並透過純數學的三角函數疊加演算法,為場景注入充滿生命力的「程序化燭火搖曳(Procedural Flicker)」動態特效!

【Step 1:打造實體壁燈與自發光材質(Emissive Material)】

我們不能只在空中放一個看不見的光源,那樣會非常沒有真實感。我們需要一個實體的 3D 模型來承載這股火光。 在 src/main.js 中,我們宣告了一個小巧的長方體作為壁燈本體,長寬高分別設定為 0.35 x 0.45 x 0.35。最關鍵的是,我們為壁燈材質啟動了「自發光(Emissive)」屬性:

const lanternGeo = new THREE.BoxGeometry(0.35, 0.45, 0.35);
const lanternMat = new THREE.MeshStandardMaterial({
  map: lanternTex,
  emissive: new THREE.Color('#ff8822'),
  emissiveIntensity: 1.6,
  roughness: 0.4,
  metalness: 0.3
});

【Step 2:點燃地城火光!佈置暖色點光源與效能優化】

有了壁燈模型後,我們需要真正能照亮周圍牆壁的「點光源(PointLight)」。 我們透過 forEach 迴圈遍歷 Day 12 儲存的 lanternPositions 陣列,精準計算出每一盞燈的三維座標。高度 ly 設定為牆壁高度 h + 0.25,剛好讓壁燈安裝在牆頂微凸的位置

const animatedLights = [];

lanternPositions.forEach((pos, idx) => {
  const h = wallHeightMap[pos.r]?.[pos.c] || 2;
  const lx = pos.c - (mapSize / 2) + 0.5;
  const lz = pos.r - (mapSize / 2) + 0.5;
  const ly = h + 0.25; // 安裝在牆頂微凸處

  // 3D 壁燈本體
  const lanternMesh = new THREE.Mesh(lanternGeo, lanternMat);
  lanternMesh.position.set(lx, ly, lz);
  lanternMesh.castShadow = true;
  scene.add(lanternMesh);

  // 溫暖的橘黃色地城火光
  const pointLight = new THREE.PointLight('#ff9d3a', 3.8, 9, 1.4);
  pointLight.position.set(lx, ly + 0.2, lz);
  // 前 4 盞主要壁燈啟用投影,兼顧畫質與極致效能
  if (idx < 4) {
    pointLight.castShadow = true;
    pointLight.shadow.bias = -0.002;
    pointLight.shadow.mapSize.width = 512;
    pointLight.shadow.mapSize.height = 512;
  }
  scene.add(pointLight);

【Step 3:注入靈魂的程序化動畫!雙頻三角函數燭火搖曳】

真實的火把光芒絕對不會是恆定不變的,它會隨著地城微風的吹拂而閃爍搖動。如果沒有動態閃爍,畫面會顯得很死板。
在生成每一盞燈時,我們不僅將它加入場景,還會把它的參考物件與隨機變數推入 animatedLights 陣列中儲存:

animatedLights.push({
    light: pointLight,
    baseIntensity: 3.8,
    speed: 4 + Math.random() * 3,
    phase: Math.random() * Math.PI * 2
  });

接著,我們進入核心渲染迴圈 animate,利用時間與三角函數,實作極富生機的程序化動畫:

const clock = new THREE.Clock();

function animate() {
  requestAnimationFrame(animate);

  const elapsedTime = clock.getElapsedTime();

  // 壁燈燭火微動搖曳 (Procedural Torch Flicker)
  animatedLights.forEach(item => {
    const flicker = Math.sin(elapsedTime * item.speed + item.phase) * 0.35 +
                    Math.cos(elapsedTime * item.speed * 2.1 + item.phase) * 0.2;
    item.light.intensity = item.baseIntensity + flicker;
  });

當你完成這段程式碼並刷新網頁時,魔法就發生了!幽暗的地城被幾盞散發著橘黃色暖光的壁燈點亮,火光在冷藍色的石牆上跳動搖曳,將地城襯托得無比深邃且充滿活著的氣息。

【本日結語】
這座專屬於你的 HD-2D 地宮,在場景建構與光影表現上已經正式完工。明天Day 15,也就是我們場景的最後一步,我們將把這一切打包進最終的運鏡迴圈,完成相機的精準鎖定機制,準備迎接主角的華麗登場,同時也會附上完整程式碼以及實際的場景畫面,大家敬請期待。


上一篇
Day 13:電影級 HD-2D 光影系統!軟陰影映射、指數迷霧與地下城光影三重奏
下一篇
Day 15:萬事俱備!動態鏡頭鎖定系統與 HD-2D 地城完工總結
系列文
Three.js × WebGL 實戰:打造 HD-2D 像素地下城與即時光影系統 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言